数字电路 - 电路代码与RTL语言
November 5, 2025
Table of Contents
Table of Contents
编码、译码和选择
编码,译码和选择这三个电路都是组合逻辑电路里的标准模块,就像乐高里的标准零件一样,用处非常大。
编码 (器) - 把分散变集中
1. 核心思想是什么?
- 问题:想象一下你的键盘有104个按键,如果每个按键都单独连一根线到电脑主板,那得多少根线?太浪费了!
- 编码器的作用:就是用少量的几根线来表示大量的输入状态。它把每一个输入信号(比如你按下的某个键)转换成一个唯一的二进制代码。
- 一句话总结:多输入 -> 少输出,给每个输入一个“代号”。
2. 普通编码器
- 例子:数字
0-9对应7段数码管的显示。- 输入: D₀, D₁, …, D₉ (10个输入,代表你想显示哪个数字)。
- 输出: a, b, c, d, e, f, g (7个输出,控制数码管的哪几段亮)。
- 比如,当输入
D₁有效时(你想显示数字1),输出端b和c就要有效(让这两段亮起来)。 - 我们可以为每一段写出逻辑表达式,比如
a段在显示 0, 2, 3, 5, 6, 7, 8, 9 时都会亮,所以它的逻辑就是:
a = D₀ + D₂ + D₃ + D₅ + D₆ + D₇ + D₈ + D₉
3. 优先编码器 (Priority Encoder) - 解决“冲突”
-
问题:如果普通编码器有多个输入同时有效(比如你不小心同时按下了
A和S键),输出的二进制码就会混乱,不知道该听谁的。 -
优先编码器的作用:它为所有输入信号设定了一个优先级。当有多个输入同时有效时,它只理会优先级最高的那个,并输出它的二进制代码。
-
看懂紧凑真值表 :

X代表“无关项 (Don’t Care)”,意思是“我不在乎这个输入是0还是1”。- 比如表格里
D₃的优先级最高。只要D₃=1,不管D₂,D₁,D₀是什么,输出就是D₃的编码11(假设编码是 A₁A₀)。 V(Valid) 是一个有效位,通常用来表示“当前有没有任何一个输入是有效的”。
译码 (器) - 把代号变回信号
1. 核心思想是什么?
- 译码是编码的逆过程。
- 问题:电脑给你一个二进制代码(比如学号
011),你怎么用这个代码准确地找到对应的那个人(比如3号同学)? - 译码器的作用:将输入的少量二进制代码,转换成大量的、唯一的输出信号。它把输入的“代号”翻译成具体的指令或位置。
- 一句话总结:少输入 -> 多输出,根据代号激活唯一一个目标。
2. 它是如何工作的?
-
一个
n输入的译码器,最多可以有2ⁿ个输出。 -
本质:译码器就是一个最小项生成器。每一个输出引脚都对应一个唯一的输入组合(即一个最小项)。
-
例子:3-8译码器



- 有3个输入 (A₂, A₁, A₀) 和8个输出 (D₀ - D₇)。
- 当输入为
101(二进制的5) 时,只有输出引脚D₅会被激活(比如变为高电平1),其他所有输出都保持无效(低电平0)。
3. 译码器的妙用
-
用小译码器搭大译码器:就像 PPT 里展示的,可以用两个2-4译码器和一个1-2译码器(或使能端)来搭建一个3-8译码器。这是层次化设计思想的体现。
-
实现任何组合逻辑函数:这是译码器一个非常强大的功能!
-
因为译码器能产生所有最小项,所以任何一个可以写成“最小项之和”的函数,都可以用一个译码器 + 一个或门来实现。
-
例子:实现1位全加器

- 和
S = Σm(1, 2, 4, 7) - 进位
C = Σm(3, 5, 6, 7) - 我们只需要把译码器的
D₁, D₂, D₄, D₇这四个输出接到一个或门,这个或门的输出就是S。同理,把D₃, D₅, D₆, D₇接到另一个或门,输出就是C。
- 和
-
选择 (器) / 多路复用器 (MUX) - 多选一的开关
1. 核心思想是什么?
- 问题:你家里有多台设备(电视、电脑、游戏机),但只有一个音响。你希望通过一个开关来决定,当前是哪台设备的声音从音响里播放出来。
- 选择器的作用:它就像一个数据选择开关。它有很多路“数据输入”,几根“选择输入”,和一路“输出”。“选择输入”的二进制值决定了哪一路“数据输入”会被连接到唯一的“输出”上。
- 一句话总结:根据选择信号,从多个输入中选一个送到输出。
2. 它是如何工作的?
-
一个
2ⁿ输入的 MUX,需要n根选择线。 -
例子:2-1 MUX

- 有2路数据输入 (I₀, I₁),1根选择线 (S),1路输出 (Y)。
- 逻辑表达式是:
Y = S'·I₀ + S·I₁ - 当
S=0,Y = 1·I₀ + 0·I₁ = I₀,输出接通 I₀。 - 当
S=1,Y = 0·I₀ + 1·I₁ = I₁,输出接通 I₁。
-
紧凑真值表
- 这种真值表更直观,直接写出当选择信号
S为某个值时,输出Y等于哪个输入。
- 这种真值表更直观,直接写出当选择信号
| S | Y |
|---|---|
| 0 | I₀ |
| 1 | I₁ |
3. 选择器的妙用
- 实现组合逻辑函数:和译码器一样,选择器也可以用来实现任何组合逻辑函数,而且有时候更灵活、更省成本。
- 基本思路 (以3变量函数 F (A, B, C) 为例):
- 用一个 4-1 MUX。
- 把变量
A和B接到 MUX 的选择端S₁和S₀。 - 根据 A, B 的取值 (00, 01, 10, 11),看函数 F 与剩下的变量
C是什么关系 (0,1,C还是C')。 - 把这个关系(
0,1,C或C')连接到对应的 MUX 数据输入端 (I₀, I₁, I₂, I₃)。
- 这种方法非常巧妙,可以大大减少电路的复杂性。
关系
- 编码器:多变少,负责“压缩信息”。
- 译码器:少变多,负责“解释信息”。
- 选择器 (MUX):多选一,负责“切换信息”。
这三个都是数字系统里不可或缺的基础模块。搞懂了它们各自的使命和工作原理,很多复杂的电路问题都能迎刃而解。
迭代电路与补码加减法器
迭代组合电路
核心思想
- 问题: 对于位数很宽的操作数电路(如64位加法器),使用传统的真值表方法是不可行的,因为真值表会变得异常庞大(例如,64位加法器输入有129位,真值表高达 2129 行)。
- 解决方案: 将“宽操作数”电路分解为由许多相同的“窄操作数”单元电路组成的阵列。
- 迭代方法: 利用每位操作所具有的相似性和单元结构的规整性,通过阵列结构将多个子电路(单元)连接起来,从而简化设计。
- 迭代阵列 (Iterative Array): 连接单元的架构,可以是一维、二维或三维结构。
一维迭代结构示例
一维迭代结构通常将前一个单元的输出作为后一个单元的输入,实现信息的逐位处理和传递。
二进制加法器
半加器
- 功能: 将两个二进制位
X和Y相加。 - 输出:
- 本位和
S(Sum) - 进位
C(Carry)
- 本位和
- 逻辑表达式:
S = X ⊕ YC = X · Y
- 电路图与真值表:

全加器
- 功能: 将三个二进制位
X、Y和低位来的进位Z(Cin) 相加。 - 输出:
- 本位和
S(Sum) - 向高位的进位
C(Cout)
- 本位和
- 逻辑表达式:
S = X ⊕ Y ⊕ ZC = XY + (X ⊕ Y)Z或C = XY + XZ + YZ
- 结构: 一个全加器可以由两个半加器和一个或门构成。

行波进位加法器
- 结构: 由多个(如4个)1位全加器迭代构成。
- 工作方式: 低位的进位信号
C作为高一位全加器的输入信号,像波浪一样逐位向高位传递。 - 特点:
- 优点: 结构简单,成本低。
- 缺点: 速度很慢,因为高位的计算必须等待所有低位的进位信号都确定后才能进行。

补码加减法
1. 机器数与带符号数
- 真值: 用“+”、“-”表示符号的实际数值。
- 机器数: 数字在计算机中的二进制表示形式,通常用最高位作为符号位。
- 符号位: 0表示正数,1表示负数。
- 编码方式:
- 正数: 原码、反码、补码表示相同,即“0”+数值的绝对值。
- 负数: 原码、反码、补码有不同的表示形式。
- **原码 : 符号位为1,其余位为数值的绝对值。
- **反码 : 符号位为1,其余位为数值绝对值按位取反。
- **补码 : 反码的末位加1。
2. 补码加减法运算
- 核心思想: 将减法运算转换为加法运算。
A - B等价于A + (-B)。在电路中,这意味着减去一个数等于加上这个数的补数。 - 运算法则:
[A+B]_补 = [A]_补 + [B]_补[A-B]_补 = [A]_补 + [-B]_补- 符号位与数值位一同参与运算。
- 运算结果如果超出最高位(即符号位)产生进位,则该进位直接丢弃。

3. 溢出
- 定义: 计算结果超出了机器所能表示的数的范围。
- 溢出检测逻辑: 仅当两个符号相同的数相加时才可能发生溢出。可以通过检测最高有效位(符号位)的进位
C_n和次高有效位的进位C_(n-1)来判断。 - 溢出判断公式:
V = C_n ⊕ C_(n-1)- 当
V=1时,表示发生溢出。 - 当
V=0时,表示未发生溢出。
- 当
4. 补码加减法器
通过使用异或门(XOR)来控制加数 B 是否取反,并利用控制信号同时作为最低位的进位输入,可以用一套电路同时实现加法和减法。
- 当控制信号
S=0时,执行A+B。 - 当控制信号
S=1时,执行A-B(即A + B的反码 + 1)。

Verilog HDL
核心思维转变:从软件到硬件
在学习 Verilog 之前,必须建立一个认知:你不是在写代码,你是在画电路。
- 软件(C/Python): 一行行顺序执行(CPU 读一行跑一行)。
- Verilog: 并发执行(Parallel)。所有的
assign语句、所有的模块实例、所有的always块,在电路通电的那一刻,是同时工作的。
模块(Module):电路的“黑盒子”
Verilog 的基本单位是“模块”。你可以把它想象成一块芯片,或者一个封装好的电路板。
基础骨架
module 模块名 (
// 1. 端口列表:对外的引脚
input clk, // 输入信号
input [7:0] a, // 8位宽的输入信号
output [7:0] sum // 8位宽的输出信号
);
// 2. 内部变量声明:盒子里的连线和存储
wire [7:0] temp;
reg [7:0] result;
// 3. 功能描述:电路逻辑
// ... (逻辑代码)
endmodule
关键要素
- 标识符:区分大小写(
a和A不一样)。通常以字母或下划线开头。 - 四值逻辑:
0:低电平1:高电平X:未知/不定态(通常因未初始化或逻辑冲突产生,调试时的噩梦)。Z:高阻态(断路状态,常用于三态门总线)。
数据类型:Wire vs Reg (重难点)
这是新手最容易混淆的地方:
| 类型 | 关键字 | 物理含义 | 赋值场景 |
|---|---|---|---|
| 线网型 | wire | 物理连线。它不存数据,只负责导电。 | 必须配合 assign 语句使用。 |
| 寄存器型 | reg | 数据存储(类似变量)。保持当前值直到下次赋值。 | 必须在 过程块 (always, initial) 内部被赋值。 |
💡 记忆口诀:
- 用了
assign必须定义为wire。- 用了
always或initial左边被赋值的必须定义为reg。
描述电路的三种方式
PPT 中用 4选1 多路选择器 (MUX) 展示了 Verilog 描述电路的三种层次:
A. 结构化描述 (Structural) - “像搭积木”
- 方法:直接调用 Verilog 内置的 12种基本门级原语 (Primitives)。
- 内置原语列表:
- 多输入门:
and,nand,or,nor,xor,xnor- 用法:
and (out, in1, in2, ...)(第一个是输出,后面全是输入)
- 用法:
- 多输出门:
buf(缓冲器),not(非门) - 三态门:
bufif1,bufif0,notif1,notif0(带控制端的门)
- 多输入门:
- 特点:代码最长,最接近底层电路图,平时写设计很少用,但看网表(Netlist)时会用到。

B. 数据流描述 (Dataflow) - “写公式”
- 核心关键字:
assign(持续赋值语句)。 - 特点: 描述信号的流动,类似布尔代数公式。只要右边变化,左边立刻更新(并发)。
- 常用场景: 组合逻辑。
- 例子:
// 逻辑表达式法 assign Y = (~S[1] & ~S[0] & I[0]) | ... ; // 条件运算法 (更常用) assign Y = (S == 2'b00) ? I[0] : (S == 2'b01) ? I[1] : ... ;
C. 行为描述 (Behavioral) - “写逻辑”
- 核心关键字:
always。 - 特点: 像写软件一样用
if-else,case描述功能,编译器会自动帮你生成对应的电路。 - 例子:
always @(S or I) begin case (S) 2'b00: Y = I[0]; 2'b01: Y = I[1]; // 必须写 default 防止锁存器 default: Y = 1'bx; endcase end
过程语句:initial 与 always
initial
- 执行次数: 只执行一次。
- 用途: 几乎只用于仿真测试 (Testbench),用来给信号赋初值。不能用于可综合的硬件电路设计(因为电路通电后无法记住”只做一次”)。
always (核心)
- 执行方式: 只要满足触发条件,就不断重复执行。
- 敏感列表
@(...):- 组合逻辑:
always @(*)或always @(a or b)。只要输入变了,里面就跑一遍。 - 时序逻辑:
always @(posedge clk)。只有时钟上升沿那一瞬间执行(打节拍)。

- 组合逻辑:
赋值的深坑:阻塞 vs 非阻塞
这是必考题,也是电路跑飞的常见原因。
阻塞赋值 (=)
- 符号:
= - 行为: 顺序执行。上一句执行完,才执行下一句。
- 场景: 用于 组合逻辑(
always @(*))。
非阻塞赋值 (<=)
- 符号:
<= - 行为: 并行执行。块内所有语句的右值先计算好,在时钟沿到来时同时更新给左值。
- 场景: 用于 时序逻辑(
always @(posedge clk))。
⚠️ 黄金法则 (Golden Rule):
- 描述时序电路(带时钟 clk 的),一律用
<=。- 描述组合电路(不带 clk 的),一律用
=。- 绝对不要在同一个 always 块里混用这两种赋值。
重要语法细节
条件语句 (if/case)
- if-else / case:只能在 initial 或 always 内部使用。
- 严重警告:描述组合逻辑时,如果 if 没有 else,或者 case 没有 default,综合器会生成锁存器 (Latch)。这是设计大忌!
- 原理:如果你没告诉电路“其他情况”输出什么,电路只能“保持上一次的值”,这就变成了锁存器。
模块实例化 (Instantiation)
这是实现层次化设计(自顶向下/自底向上)的关键。
- 核心概念:像在电路板上插芯片一样,把写好的模块(如 half_adder)放到当前模块里。
- 两种关联方式:
-
位置关联:
module_name u1 (a, b, c);- 缺点:必须严格记住端口顺序,容易出错。
-
名称关联 (推荐):
module_name u1 (.端口名(信号名), .端口名(信号名));- 优点:不容易接错线,顺序乱了也没事。
怎么把小模块连成大系统?
建议使用名称关联(不易出错),不要用位置关联。
- 优点:不容易接错线,顺序乱了也没事。
-
// 推荐:名称关联 (.端口名(信号名))
half_adder u1 (
.a (signal_a),
.b (signal_b),
.sum(signal_sum)
);
// 不推荐:位置关联 (容易接错线)
half_adder u2 (signal_a, signal_b, signal_sum);
常用运算符与数字表示

PPT 给出了详细的表格,这里按优先级从高到低整理(越靠上优先级越高):
| 类别 | 符号 | 说明 | 例子 |
|---|---|---|---|
| 取反/非 | !, ~ | !逻辑非, ~按位取反 | !a (真变假), ~a (1010变0101) |
| 算术 | *, /, % | 乘、除、取模 | 硬件设计尽量少用除法和取模 |
| 算术 | +, - | 加、减 | |
| 移位 | <<, >> | 左移、右移 | a << 2 (左移2位) |
| 关系 | <, <=, >, >= | 小于、小于等于… | 返回结果是 1 或 0 |
| 相等 | ==, != | 等于、不等于 | a == b |
| 按位与 | & | 每一位都进行与操作 | 4’b1100 & 4’b1010 = 4’b1000 |
| 按位异或 | ^, ^~ | 异或、同或 | |
| 按位或 | ` | ` | |
| 逻辑与 | && | 逻辑判断 | (a>b) && (b>c) |
| 逻辑或 | ` | ` | |
| 三目(条件) | ? : | 常见 | a ? b : c (a真则b,否则c) |
| 拼接符 | {a, b},{} | 将信号拼接 | {cout, sum} = a + b 利用拼接位接收进位 |
实战项目:使用 Verilog 设计带暂停和复位功能的数字秒表(0-9 计数器)
这是一个非常经典的入门级实战项目:带暂停和复位功能的数字秒表(0-9 计数器)。
这个例子非常完美,因为它涵盖了 Verilog 学习中最核心的知识点:
- 时序逻辑(计数器):用到
always @(posedge clk)和非阻塞赋值<=。 - 组合逻辑(数码管解码):用到
always @(*)、case语句和阻塞赋值=。 - 层次化设计(模块实例化):如何把两个小模块连起来。
- 控制信号:如何处理复位(Reset)和使能(Enable)。
这里的电路结构图
我们将设计一个顶层模块 Top_Stopwatch,它内部包含两个子模块:
Counter_10:负责核心计数逻辑(0, 1, … 9, 0 …)。Decoder_7Seg:负责把数字翻译成数码管能亮的信号。
[顶层模块 Top_Stopwatch]
|
输入 --+--> [ 子模块1: 计数器 ] --(wire data)---> [ 子模块2: 译码器 ] --+--> 输出
(clk, | (Counter_10) (Decoder_7Seg) | (seg_led)
rst, | |
start) +-----------------------------------------------------------+
Verilog 代码分析
代码核心解析
1. 为什么有的地方用 reg,有的用 wire?
这是在 Top 模块中最容易晕的地方。
- 在 Top 模块里:
current_num是一根物理连线,连接两个子模块,所以它是wire。 - 在 Counter 模块内部:
cnt_out需要在always块里被赋值,并且需要在时钟跳变前记住上一次的值(比如上一次是3,这次才能变成4),所以它必须是reg。
2. 为什么计数器用 <=,译码器用 =?
- 计数器 (Sequential):它需要“打拍子”。在时钟上升沿到来的那一瞬间,我们希望所有的寄存器同时更新状态,不分先后,所以用非阻塞赋值
<=。 - 译码器 (Combinational):它是一个“翻译机”。只要输入变了(比如3变成了4),输出马上就要跟着变,不需要等时钟,逻辑上有先后依赖关系,所以用阻塞赋值
=。
3. 这里的 rst_n 是什么意思?
rst是 Reset 的缩写。_n或_b后缀通常表示 Active Low(低电平有效)。if (!rst_n):意思是如果rst_n是 0(按下了复位键),条件成立,执行复位操作。这是硬件设计的行业规范。
4. 实例化时的 .a(b) 格式
Counter_10 u_counter (
.clk (sys_clk),
...
);
.clk是子模块里定义的名字(插座名)。(sys_clk)是顶层模块里的信号名(插头名)。- 这种写法叫名称关联,即使你把顺序写乱了,编译器也能正确连上线。千万不要用位置关联(只写括号里的),那个容易出错。
实现代码
1. 子模块一:核心计数器 (Counter_10. v)
点击查看代码
// 模块功能:带异步复位和暂停功能的十进制计数器
module Counter_10 (
input clk, // 时钟信号
input rst_n, // 复位信号 (n表示低电平有效,即0为复位)
input en, // 使能信号 (1=开始计数,0=暂停)
output reg [3:0] cnt_out // 4位输出 (0~9只需要4位,最大是1001)
);
// ======= 时序逻辑电路 =======
// 黄金法则:时序逻辑用非阻塞赋值 (<=)
// 敏感列表:时钟上升沿 OR 复位下降沿 触发
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 1. 异步复位:最高优先级
// 当 rst_n 为 0 时,不管时钟怎样,立马清零
cnt_out <= 4'd0;
end
else if (en) begin
// 2. 正常计数逻辑:只有 en 为 1 时才工作
if (cnt_out == 4'd9)
cnt_out <= 4'd0; // 计满到9,归零
else
cnt_out <= cnt_out + 1'b1; // 没满,加1
end
// else (en==0) 的情况省略了
// 在时序逻辑中,如果不写 else,默认保持原值(锁存上一刻的状态),这正是暂停功能想要的
end
endmodule
2. 子模块二:数码管译码器 (Decoder_7Seg. v)
点击查看代码
// 模块功能:将4位二进制数转换为7段数码管的控制信号
// 假设是共阴极数码管:1亮,0灭
module Decoder_7Seg (
input [3:0] data_in, // 输入的数字 (0-9)
output reg [6:0] seg_out // 输出的7段段码 (a,b,c,d,e,f,g)
);
// ======= 组合逻辑电路 =======
// 黄金法则:组合逻辑用阻塞赋值 (=)
// 敏感列表:* 代表只要输入(data_in)变化,就执行
always @(*) begin
case (data_in)
// 根据真值表翻译
// 格式:数字 : 输出 = 7位二进制段码 (对应 gfe_dcba)
4'd0 : seg_out = 7'b011_1111; // 显示 0
4'd1 : seg_out = 7'b000_0110; // 显示 1
4'd2 : seg_out = 7'b101_1011; // 显示 2
4'd3 : seg_out = 7'b100_1111; // 显示 3
4'd4 : seg_out = 7'b110_0110; // 显示 4
4'd5 : seg_out = 7'b110_1101; // 显示 5
4'd6 : seg_out = 7'b111_1101; // 显示 6
4'd7 : seg_out = 7'b000_0111; // 显示 7
4'd8 : seg_out = 7'b111_1111; // 显示 8
4'd9 : seg_out = 7'b110_1111; // 显示 9
// 缺省处理:防止生成锁存器(Latch)
default: seg_out = 7'b000_0000; // 其他情况全灭
endcase
end
endmodule
3. 顶层模块:组装电路 (Top_Stopwatch. v)
点击查看代码
// 模块功能:顶层封装,连接计数器和译码器
module Top_Stopwatch (
input sys_clk, // 系统时钟 (比如 FPGA 板上的 50MHz)
input sys_rst_n, // 系统复位按键
input sw_start, // 开始/暂停开关
output [6:0] led_pins // 连接到物理数码管引脚
);
// ==== 1. 定义内部连线 (Wire) ====
// 这根线用来把 计数器的输出 和 译码器的输入 连起来
// 注意:因为只是连线,不存数据,所以必须用 wire
wire [3:0] current_num;
// ==== 2. 实例化计数器 (就像把芯片插在板子上) ====
Counter_10 u_counter (
.clk (sys_clk), // 端口连接:模块端口(外部信号)
.rst_n (sys_rst_n),
.en (sw_start),
.cnt_out (current_num) // 输出连到内部网线 current_num 上
);
// ==== 3. 实例化译码器 ====
Decoder_7Seg u_decoder (
.data_in (current_num), // 输入从内部网线 current_num 取值
.seg_out (led_pins) // 结果直接送到芯片外部引脚
);
endmodule